package net

/*
 * net session factory and message handler.
 */
import (
	"GameServer/role"
	"GameServer/timer"
	"GameServer/timewheel"
	"mnet"
	"sync"
	"sync/atomic"
	"time"

	log "myLog"
)

const (
	gMaxPacketSize  = 100 * 1024
	gObjIdSize      = 4
	gMsgBeatHeartId = 0
	gBeatHeartTime  = 120 // unit: second.
)

// Session attach struct.
type clientSessionAttach struct {
	pRole             *role.Role           // attach role object.
	objId             uint32               // object id.
	lastBeatHeartTime time.Time            // last beat heart time.
	beatHeartTimerId  uint32               // beat heart timer id.
	beatHearTimer     *timewheel.TimerTask // beat heart timer.
}

func (csa *clientSessionAttach) GetObjId() uint32 {
	return csa.objId
}
func (csa *clientSessionAttach) SetObjId(id uint32) {
	csa.objId = id
}

func (csa *clientSessionAttach) GetLastBeatHeartTime() time.Time {
	return csa.lastBeatHeartTime
}
func (csa *clientSessionAttach) SetLastBeatHeartTime(t time.Time) {
	csa.lastBeatHeartTime = t
}
func (csa *clientSessionAttach) GetBeatHeartTimerId() uint32 {
	return csa.beatHeartTimerId
}
func (csa *clientSessionAttach) SetBeatHeartTimerId(id uint32) {
	csa.beatHeartTimerId = id
}

// SetBeatHeartTimer sets beat heart timer.
func (csa *clientSessionAttach) SetBeatHeartTimer(cb *timewheel.TimerTask) {
	csa.beatHearTimer = cb
}

// client session factory.
type clientFactory struct {
	nextId     atomic.Uint32
	sessionMap sync.Map
}

func (cf *clientFactory) Init() {
	clientHandlerObj.Init()
	cf.nextId = atomic.Uint32{}
}

// CreateSession creates TCP session.
func (cf *clientFactory) CreateSession() mnet.ISession {
	objId := cf.nextId.Add(1)
	session := mnet.CreateSession()
	roleObj := role.RoleMgrObj.CreateRole()
	attach := &clientSessionAttach{
		pRole: roleObj,
		objId: objId,
	}

	roleObj.SetState(role.RoleStateConnected)
	roleObj.SetSession(session)
	session.SetAttach(attach)
	session.SetDealer(clientHandlerObj.OnMessage)
	session.SetEstablish(clientHandlerObj.OnStatus)
	cf.sessionMap.Store(objId, session)
	return session
}

// Release releases tcp session.
func (cf *clientFactory) Release(sess *mnet.Session) {
	objId := sess.GetAttach().(*clientSessionAttach).GetObjId()
	cf.sessionMap.Delete(objId)
}

// Find finds session with id.
func (cf *clientFactory) Find(objId uint32) *mnet.Session {
	if v, ok := cf.sessionMap.Load(objId); ok {
		return v.(*mnet.Session)
	} else {
		return nil
	}
}

// client message handler
type clientMessageHandler struct {
	mnet.RPCMessageDealer
}

func (md *clientMessageHandler) Init() {
	md.BuildStructMethods(md)
}

// OnStatus client tcp status callback
func (md *clientMessageHandler) OnStatus(sess *mnet.Session, isConnect bool) {
	sessionAttachObj := sess.GetAttach().(*clientSessionAttach)
	objId := sessionAttachObj.GetObjId()
	if !isConnect {
		// Terminate callback.
		log.InfoAf("%d objId:%s client is terminated", objId, sess.GetConn().RemoteAddr())

		pRole := sess.Attach.(*clientSessionAttach).pRole
		pRole.SetState(role.RoleStateOffLine)
		pRole.SetSession(nil)

		ClientFactoryObj.Release(sess)
		timer.TimerMgrObj.GetTimer().KillTimer(sessionAttachObj.GetBeatHeartTimerId())
	} else {
		// Connection callback.
		log.InfoAf("%d objId:%s client is connected", objId, sess.GetConn().RemoteAddr())

		// set last beat heart.
		sessionAttachObj.SetLastBeatHeartTime(time.Now())

		// first insert to mysql.
		sessionAttachObj.pRole.Insert()

		// add beat heart check timer.
		sessionAttachObj.SetBeatHeartTimerId(timer.TimerMgrObj.GetNextId())
		timer.TimerMgrObj.GetTimer().AddRepeatTimer(sessionAttachObj.GetBeatHeartTimerId(), gBeatHeartTime, true, func(v ...interface{}) {
			if mySession, ok := v[0].(*mnet.Session); mySession != nil && ok {
				if time.Since(mySession.Attach.(*clientSessionAttach).GetLastBeatHeartTime()) >= gBeatHeartTime*time.Second {
					log.InfoAf("find %s has no beat heart, just close it", mySession.GetConn().RemoteAddr())
					mySession.Close()
					timer.TimerMgrObj.GetTimer().KillTimer(sessionAttachObj.GetBeatHeartTimerId())
				}
			}
		}, []interface{}{sess})
	}
}

// handleBeatHeart handles beat heart.
func (md *clientMessageHandler) handleBeatHeart(session *mnet.Session, sessAttachObj *clientSessionAttach, bin []byte) {
	_ = session
	_ = sessAttachObj
	_ = bin
	session.SendMsg(0, []byte("hello,world"))
}

// OnMessage client message handler.
func (md *clientMessageHandler) OnMessage(sess *mnet.Session, msgId interface{}, msg ...interface{}) {
	sessAttachObj := sess.Attach.(*clientSessionAttach)
	sessAttachObj.lastBeatHeartTime = time.Now()
	switch v := msgId.(type) {
	case uint16:
		sendMsg := msg[0].([]byte)
		if v == gMsgBeatHeartId {
			md.handleBeatHeart(sess, sessAttachObj, sendMsg)
		} else {
			log.InfoAf("receive %d msg id, data:%+v", v, sendMsg)
		}
	default:
		log.ErrorAf("Invalid type:%+v", v)
	}
}

// client message handler.
var clientHandlerObj clientMessageHandler

// client factory.
var ClientFactoryObj clientFactory
